跳到主要内容

Canvas 简单实现脏区重绘

主要使用的 API 有 getImageData()putImageData(), drawImage()

改进的流程图

选择框的思考与思路

转载自 Canvas脏矩形应用之截图 演示效果 https://codepen.io/joeoeoe/pen/JjPKEOG

其实截图功能非常简单,如下流程图:

如何进行截图比较简单,拖拽矩形时记录位置及长宽(即 rubberBandRectangle),在 mouseup 时通过 drawImage() 及 rubberBandRectangle 记录进行截图

但是,仅仅只是按照此流程图的话,用户体验会很不好,比如没有绘制矩形框标识选中区域 所以,我们一个截图应用至少应该拥有矩形框标识选中区域,而矩形框也是绘制在 Canvas 画布上,会影响对原图的截取,所以具体至 Canvas 绘图中流程图应该如下:

如果在鼠标移动过程中没有排除矩形框重绘,就会出现下面这种情况:

综上,我们有两个问题:

  1. 如何进行截图
  2. 如何重绘排除矩形干扰

如何重绘排除矩形干扰

这里我们引出脏矩形的概念

所谓脏矩形技术,就是哪里脏了,就(仅仅)把脏了的那块区域重绘。而不是每次直接性的重绘。

这里写出两种思路,第一种是 mousemove 时不断调用 getImageData(),通过 putImageData() 修复及去除矩形痕迹,第二种是 mousedown 时记录 Canvas 原图, mousemove 时通过对原图截取,进行局部重绘

《HTML5 Canvas核心技术》书中仅把第二种思路归为脏矩形技术,但在查阅相关定义之后,脏矩形更多可理解为局部重绘,所以我认为两种思路都是对脏矩形的应用。

由于 getImageData() 较慢,第二种思路的效率高于第一种;

方式一:不断调用 getImageData()

在 mousemove 阶段,我们不断执行如下(伪)代码

//去除局部的矩形痕迹
if (imageData !== null) {
context.putImageData(imageData,
rubberBandRectangle.left,
rubberBandRectangle.top);
}
......
//记录局部图像
imageData = context.getImageData(rubberBandRectangle.left,
rubberBandRectangle.top,
rubberBandRectangle.width,
rubberBandRectangle.height);
......

方式二:通过原图截取进行局部重绘

mousedown 阶段,记录原图(部分代码):

imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

接着在 mousemove 阶段,选取 imageData 局部,进行 canvas 上的局部重绘

ctx.putImageData(imageData, 0, 0,
rubberBandRectangle.left,
rubberBandRectangle.top,
rubberBandRectangle.width ,
rubberBandRectangle.height);

2D渲染引擎中的脏矩形技术

其基本条件是:脏矩形的计算和绘制的代价小于直接性重绘。换句话说,如果一个屏幕每一帧变化的东西太多的话,就不值得用这个古老的算法了。

脏矩形要解决的两个问题是: 1、什么叫脏,即什么情况下会弄脏

当我们的游戏中的元素(通常是RenderObject)发生位置,大小,方向,动画,添加,删除等操作时,那么该元素原来对应的区域会弄脏,同时,新对应的区域也同样被弄脏。

2、弄脏的区域该怎么办

把场景分成一个一个的格子(比如 12864 或者 6432,矩形格或菱形格);每个格子,有一个是否脏的标记,以及其上的 RenderObject 列表;

每个 RenderObject,可以保存一个对应的格子列表(当然,也可以计算,并且,实际上有两个格子列表,变化之前和变化之后分别对应一个格子列表,对于变化前的,对应的格子列表中的每个格子应当删除该物体,对于变化后的,对应的格子列表中的每个格子应当添加该物体)

每次绘制时,按照某种顺序,绘制脏了的格子;每个脏了的格子,按照某种顺序,将其上的 RenderObject 裁剪绘制到格子的区域上;

Reference